Skip to content

feat(rewards): surface achievement token rewards and fix rewards page display#4424

Merged
graycyrus merged 5 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/achievement-token-rewards-ui
Jul 3, 2026
Merged

feat(rewards): surface achievement token rewards and fix rewards page display#4424
graycyrus merged 5 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/achievement-token-rewards-ui

Conversation

@YellowSnnowmann

@YellowSnnowmann YellowSnnowmann commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Rewards page: fix achievement display issues and surface the per-achievement token
reward now granted by the backend.

Changes

Display fixes:

  • Render one progress badge per achievement (was hard-capped at 8, silently hiding
    3 of the 11 achievements).
  • Split the single stats block into two cards — Discord status vs. product activity —
    so Discord and platform metrics are no longer conflated.
  • Clarify the streak: "Activity streak", "{n} days" unit, an explanatory hint, and a
    Longest streak row.
  • Surface the backend per-achievement progress label ("3 / 7 active days", etc.),
    which was computed but never rendered.
  • "Roles assigned" now counts assignable achievements (unlocked + role-configured)
    instead of all unlocked, so the ratio can't misreport (e.g. "3 of 4" for a role
    that can never be granted).

Token rewards:

  • Show the reward per achievement as a pill (+500K tokens, +5M tokens/mo for
    subscriber tiers), with compact formatting.
  • Add rewardTokens / rewardRecurring to the RewardsAchievement type + API
    normalizer.

i18n:

  • New keys added to en.ts and all 13 locale files (parity kept); brand key allowlisted
    in the English-drift check.

Testing

  • Vitest rewards suite green (component, page, api, slice). typecheck / eslint /
    prettier / i18n parity clean.

Notes

  • Backend counterpart: token rewards credited as USD promo (tinyhumansai/backend).
  • Could be split into two PRs (display fixes vs. token-reward surfacing) if preferred —
    they share the same files.

Summary by CodeRabbit

  • New Features
    • Expanded community rewards with per-achievement claiming, “Claiming/Claimed” states, credited feedback, and token reward badges (including monthly).
    • Added “Activity streak” enhancements including “Longest streak” and improved streak/date wording.
    • Improved Discord details display and connection/disconnection copy in the rewards area.
  • Bug Fixes
    • Background (silent) refresh after claiming now updates rewards without forcing a full loading/error view.
    • Claim handling is more reliable: clears prior errors per achievement, supports concurrent claims, and avoids duplicate credited feedback on re-claim.
    • Fixed achievement progress display (no badge cap) and corrected “roles assigned” to reflect eligible/assignable achievements.
  • Documentation
    • Updated community rewards text across supported languages for the new claiming/streak UI.

…hievement badge rendering, and separate Discord status from activity metrics. Update translations for new features and improve test coverage for progress badges and role assignments.
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds reward claim support with claim API and UI, silent background refresh after successful claims, updated streak and Discord stats, and new/updated rewards.community translations across locales.

Changes

Reward Claim Feature

Layer / File(s) Summary
Reward claim type contracts
app/src/types/rewards.ts
Makes claim-related snapshot fields optional and adds RewardClaimResult.
Claim API and normalization
app/src/services/api/rewardsApi.ts, app/src/services/api/__tests__/rewardsApi.test.ts, app/src/store/__tests__/rewardsSlice.test.ts
Adds claimReward, expands normalization for reward/claim fields, and updates fixtures/assertions.
Community tab claim and stats UI
app/src/components/rewards/RewardsCommunityTab.tsx, app/src/components/rewards/__tests__/RewardsCommunityTab.test.tsx
Adds claim handling, per-achievement state, reward token pills, Discord details, streak stats, and matching tests.
Silent refresh wiring in Rewards page
app/src/pages/Rewards.tsx, app/src/pages/__tests__/Rewards.test.tsx
Reworks rewards loading for silent refreshes and passes the callback into the community tab with page coverage.
Streak label e2e updates
app/test/e2e/specs/rewards-progression-persistence.spec.ts, app/test/playwright/specs/rewards-progression-persistence.spec.ts
Updates streak assertions to use “Activity streak” and 14 days.

Locale Translations for Rewards Community UI

Layer / File(s) Summary
Locale string additions for claim/streak/Discord UI
app/src/lib/i18n/{ar,bn,de,en,es,fr,hi,id,it,ko,pl,pt,ru,zh-CN}.ts, scripts/i18n-find-english.ts
Adds and updates rewards.community strings for claim, streak, reward token, and Discord detail copy, plus the English allowlist.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested labels: bug, feature

Suggested reviewers: M3gA-Mind

Poem

A rabbit hopped with tokens bright,
Then claimed a prize by dashboard light.
The streak grew long, the Discord sang,
And silent refresh did its thing—clang! 🥕
New words in many tongues now gleam,
A cozy rewards-tab rabbit dream.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main changes: surfacing achievement token rewards and fixing Rewards page display issues.

Comment @coderabbitai help to get the list of available commands.

@YellowSnnowmann YellowSnnowmann marked this pull request as ready for review July 2, 2026 14:43
@YellowSnnowmann YellowSnnowmann requested a review from a team July 2, 2026 14:43

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a221bdaec9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread app/src/lib/i18n/en.ts
The streak metrics row was renamed 'Current streak' -> 'Activity streak' and its
value now renders '{n} days'. Update the Playwright + WDIO progression specs to
locate the row by the new label and assert '14 days' (chatgpt-codex-connector).
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 2, 2026
@YellowSnnowmann YellowSnnowmann changed the title feat(rewards): surface achievement token rewards and fix rewards page display WIP(rewards): surface achievement token rewards and fix rewards page display Jul 3, 2026

@sanil-23 sanil-23 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: WIP(rewards): surface achievement token rewards and fix rewards page display

Read-only review of the full PR diff (24 files) against main. Frontend-only change: RewardsCommunityTab.tsx + tests, rewardsApi/types normalization for two new fields, i18n across all 14 locales, and E2E label updates.

Walkthrough

The change (a) removes the hard 8-badge cap so the progress-circle row matches the "{unlocked} of {total}" count, (b) surfaces per-achievement token rewards (rewardTokens / rewardRecurring) as an amber pill with compact formatting (500K, 2M, +…/mo), (c) shows locked-achievement progress labels, (d) splits the single stats footer into a Discord card and an Activity card (adding longest-streak + a streak-in-days label), and (e) recomputes the "roles assigned" ratio over assignable (unlocked + role-configured) achievements. It's marked WIP in the title but is not a draft, so CI gates apply. Overall the component logic and test coverage are solid; there is one CI-blocking i18n issue that will fail the required pnpm i18n:english:check gate.

Changed-files summary

Area Files Notes
Component RewardsCommunityTab.tsx badge cap removal, reward pill, progress label, stat split, assignable-role ratio
Types/normalize types/rewards.ts, services/api/rewardsApi.ts rewardTokens: number|null, rewardRecurring: boolean, safe defaults
Tests RewardsCommunityTab.test.tsx, Rewards.test.tsx, rewardsApi.test.ts, rewardsSlice.test.ts new pill / split / cap / ratio cases + fixture fields
i18n en + 13 locales 6 new keys, currentStreak/streakDays reworded
i18n tooling scripts/i18n-find-english.ts allowlists rewards.community.discordDetails
E2E test/e2e/**, test/playwright/** "Current streak"→"Activity streak", 1414 days

Findings

🔴 Major (CI-blocking) — es and pt rewardTokens are byte-identical to English → i18n:english:check fails

app/src/lib/i18n/es.ts and app/src/lib/i18n/pt.ts both add:

'rewards.community.rewardTokens': '+{tokens} tokens',

This is byte-identical to the English value (app/src/lib/i18n/en.ts). scripts/i18n-find-english.ts flags any Latin-locale value where v === en[k] (unless allowlisted) and process.exit(1) on any hit — so the required pnpm i18n:english:check gate (per CLAUDE.md) will fail. es/pt are in LATIN_LOCALES, the value isn't isTechnical (+{tokens} tokens contains a real word after placeholder-stripping), and rewards.community.rewardTokens is not in INTENTIONAL_ENGLISH. (You already hit and fixed the same rule for discordDetails — this one was missed.)

Note rewardTokensMonthly is fine — es /mes and pt /mês differ from en /mo. Only the non-recurring key collides.

Fix — "tokens" is a legitimate loanword in es/pt, so allowlist the key alongside discordDetails in scripts/i18n-find-english.ts:

   "rewards.community.discordDetails", // "Discord" — brand/product name, same in every locale
+  "rewards.community.rewardTokens", // "tokens" — accepted loanword; identical in es/pt

(Alternatively translate them, e.g. es/pt +{tokens} fichas, but the loanword is the more natural choice for a token economy.) Please run pnpm i18n:english:check locally to confirm it passes.


Nitpicks / questions

  • summary.assignedDiscordRoleCount is now dead for the UI. The component previously read snapshot.summary.assignedDiscordRoleCount; it now derives assignedRoleCount locally from achievements (RewardsCommunityTab.tsx:128). The backend field is still normalized (rewardsApi.ts:135) and asserted in tests but no longer rendered. Intended? If the backend count and the locally-derived count can diverge, worth a one-line comment on why the local derivation wins (consistency with the assignableRoleCount denominator). — minor / question
  • Badge aria-label on a plain div. RewardsCommunityTab.tsx:349 puts aria-label={role?.title} on a non-interactive div with no role; screen-reader support for aria-label on a generic container is inconsistent. Consider role="img" so the label is reliably announced. — nitpick
  • Backend contract for the pills. The pills only render when /rewards/me returns rewardTokens/rewardRecurring; normalization defaults them to null/false, so the UI degrades gracefully if the backend hasn't shipped yet. Confirm the backend actually emits these fields before un-WIP-ing, otherwise the headline feature is invisible in prod. — question (WIP)
  • WIP title, ready for review. PR is isDraft: false with a WIP(...) title, so all merge gates (coverage ≥80% changed lines, i18n, lint) apply now. Either mark it draft or drop the WIP prefix once the item above is fixed. — nitpick

Looks good

  • Removing the 8-cap and switching to overflow-x-auto correctly fixes the silent badge truncation; the (RewardsAchievement | null)[] typing + consistent optional-chaining is clean.
  • formatTokens (compact, maximumFractionDigits: 1, Math.max(0, trunc)) matches formatNumber's locale handling and the 500K/2M/5M test expectations.
  • The assignable-role ratio (unlocked && roleId, assigned counted within that set) is correct and well-commented — it can no longer report "3 of 4" for a role that can never be granted.
  • i18n parity is complete: all 14 locales carry the 6 new keys and the reworded currentStreak/streakDays, with real (non-placeholder) translations and correct native scripts. pnpm i18n:check (gates only on missing/extra keys) will pass.
  • Test coverage is thorough — cap removal, per-locked progress label, two-card split, days-labelled streaks, one-time vs /mo pill, and the assignable-ratio edge case are each exercised. E2E/Playwright specs updated in lockstep with the label rename.

Note: I couldn't execute the i18n scripts locally (no node_modules in this worktree), so the es/pt collision is verified by byte-comparison + reading the checker's flag logic rather than a live run — please confirm with pnpm i18n:english:check.

@sanil-23 sanil-23 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for one CI-blocking item (full review posted above).

🔴 es and pt rewards.community.rewardTokens are byte-identical to the English +{tokens} tokens, so the required pnpm i18n:english:check gate (scripts/i18n-find-english.ts, process.exit(1) on any Latin-locale value equal to en) will fail. The key isn't allowlisted — same rule you already fixed for discordDetails in this PR.

Fix (allowlist the loanword, matching discordDetails):

   "rewards.community.discordDetails", // "Discord" — brand/product name, same in every locale
+  "rewards.community.rewardTokens", // "tokens" — accepted loanword; identical in es/pt

Please run pnpm i18n:english:check to confirm green. The rest of the change (parity across all 14 locales, component logic, and test coverage) looks good — see the detailed review above for minor/optional items.

…fresh and error handling

- Added  API method to handle reward claims.
- Introduced  utility for better error messaging.
- Enhanced  to support claiming rewards, including state management for claiming and feedback.
- Updated translations for new claim-related strings in multiple languages.
- Improved test coverage for claiming functionality and silent refresh behavior.
@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label Jul 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
app/src/services/api/rewardsApi.ts (1)

154-154: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Same claimableCount optionality concern as rewards.ts.

This is the actual implementation that always overwrites claimableCount with asNumber(...), regardless of whether rawSummary.claimableCount was present in the payload. This is the root cause of the type/behavior mismatch flagged in app/src/types/rewards.ts (Line 27-28) — the field's optionality can never manifest as undefined through this normalizer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/services/api/rewardsApi.ts` at line 154, The rewards API normalizer
is forcing `claimableCount` to always be set via `asNumber(...)`, so the
optional `undefined` state from the payload is lost. Update the `rewardsApi`
summary mapping so `claimableCount` is only assigned when
`rawSummary.claimableCount` is actually present, and leave it unset otherwise;
keep the logic aligned with the `claimableCount` typing/behavior used by the
rewards types and the normalizer function handling this summary object.
🧹 Nitpick comments (1)
app/src/components/rewards/RewardsCommunityTab.tsx (1)

35-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate error-extraction logic vs Rewards.tsx's errorMessage().

claimErrorMessage re-implements the same 'error' in errinstanceof Error → fallback chain as errorMessage() in app/src/pages/Rewards.tsx (Line 21-29). Consider extracting a shared helper (e.g. app/src/utils/apiErrors.ts) with an optional fallback param, used by both call sites.

♻️ Proposed shared helper
+// app/src/utils/apiErrors.ts
+export function extractApiErrorMessage(err: unknown, fallback: string): string {
+  if (
+    err &&
+    typeof err === 'object' &&
+    'error' in err &&
+    typeof (err as { error?: unknown }).error === 'string'
+  ) {
+    return (err as { error: string }).error;
+  }
+  if (err instanceof Error && err.message) return err.message;
+  return fallback;
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/rewards/RewardsCommunityTab.tsx` around lines 35 - 48,
`claimErrorMessage` in `RewardsCommunityTab` duplicates the same
error-unwrapping flow used by `errorMessage()` in `Rewards.tsx`; extract that
shared `'error' in err` / `instanceof Error` / fallback logic into a common
helper (for example in a shared utility module) with an optional fallback
argument, then update both `claimErrorMessage` and `errorMessage()` to call it
so the behavior stays consistent in both places.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/rewards/RewardsCommunityTab.tsx`:
- Around line 134-136: `claimingId` in `RewardsCommunityTab` is a shared scalar
and causes overlapping claim requests to overwrite each other. Change the
claim-state tracking in the claim handler/finally path to use a Set (or
equivalent per-achievement in-flight map) so multiple achievement ids can remain
pending independently, and update the button-disabled logic that currently
checks `claimingId` so it looks up the specific achievement id in that
collection. Make sure the `finally` cleanup only removes the completed id rather
than clearing all claim state.

In `@app/src/lib/i18n/ru.ts`:
- Around line 3694-3695: The reward token strings in ru.ts are inconsistent
because rewardTokens and rewardTokensMonthly still contain the English word
“tokens”; update these entries to match the existing Russian terminology used
elsewhere in the file, aligning them with claimTokens and other token-related
translations. Locate the affected locale entries by their keys
rewards.community.rewardTokens and rewards.community.rewardTokensMonthly and
replace the untranslated word so the phrasing is consistent across ru.ts.

---

Duplicate comments:
In `@app/src/services/api/rewardsApi.ts`:
- Line 154: The rewards API normalizer is forcing `claimableCount` to always be
set via `asNumber(...)`, so the optional `undefined` state from the payload is
lost. Update the `rewardsApi` summary mapping so `claimableCount` is only
assigned when `rawSummary.claimableCount` is actually present, and leave it
unset otherwise; keep the logic aligned with the `claimableCount`
typing/behavior used by the rewards types and the normalizer function handling
this summary object.

---

Nitpick comments:
In `@app/src/components/rewards/RewardsCommunityTab.tsx`:
- Around line 35-48: `claimErrorMessage` in `RewardsCommunityTab` duplicates the
same error-unwrapping flow used by `errorMessage()` in `Rewards.tsx`; extract
that shared `'error' in err` / `instanceof Error` / fallback logic into a common
helper (for example in a shared utility module) with an optional fallback
argument, then update both `claimErrorMessage` and `errorMessage()` to call it
so the behavior stays consistent in both places.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 94699fe1-8ff4-438e-85a2-6e1243ac060f

📥 Commits

Reviewing files that changed from the base of the PR and between 6e17240 and af6bf68.

📒 Files selected for processing (25)
  • app/src/components/rewards/RewardsCommunityTab.tsx
  • app/src/components/rewards/__tests__/RewardsCommunityTab.test.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/Rewards.tsx
  • app/src/pages/__tests__/Rewards.test.tsx
  • app/src/services/api/__tests__/rewardsApi.test.ts
  • app/src/services/api/rewardsApi.ts
  • app/src/store/__tests__/rewardsSlice.test.ts
  • app/src/types/rewards.ts
  • app/test/e2e/specs/rewards-progression-persistence.spec.ts
  • app/test/playwright/specs/rewards-progression-persistence.spec.ts
  • scripts/i18n-find-english.ts

Comment thread app/src/components/rewards/RewardsCommunityTab.tsx Outdated
Comment thread app/src/lib/i18n/ru.ts
- Updated state management to track multiple in-flight claims using a Set, allowing independent button disabling for each achievement.
- Modified claim handling logic to ensure that only the relevant claim's button is re-enabled upon completion.
- Added a new test case to verify that pending claims do not affect the state of other claims.
- Updated translations to include new token-related strings.
@YellowSnnowmann YellowSnnowmann changed the title WIP(rewards): surface achievement token rewards and fix rewards page display feat(rewards): surface achievement token rewards and fix rewards page display Jul 3, 2026
@YellowSnnowmann

Copy link
Copy Markdown
Contributor Author

@sanil-23 Thanks for the catch — fixed in ebb1489.

rewards.community.rewardTokens is now allowlisted in scripts/i18n-find-english.ts alongside discordDetails, since "tokens" is the technical unit we keep verbatim in every locale. Only the base key collided — the recurring variant rewardTokensMonthly differs per locale (/mo → es /mes, pt /mês), so it was never flagged. Ran pnpm i18n:english:check locally and rewards.community.rewardTokens no longer appears in the output.

@sanil-23 sanil-23 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review (head ebb1489, retitled feat(...), now +1115/-79)

Re-reviewed the delta since my last pass (2 new commits: feat(rewards): implement reward claiming… + refactor: … concurrent claims).

✅ Blocker resolved

rewards.community.rewardTokens is now allowlisted in scripts/i18n-find-english.ts, so pnpm i18n:english:check no longer trips on the es/pt byte-identical values. I re-verified all i18n for the 5 new claim keys (claimTokens, claiming, claimed, claimCredited, claimError): full parity across all 14 locales, real translations, native scripts present for the 6 non-Latin locales, and no Latin value collides with English. i18n is clean. 👍

New claim feature — assessment

The claiming flow is genuinely well-engineered:

  • Concurrency is correct — in-flight claims tracked as a Set<id> with functional setState updates; each button disables/clears independently. The "one pending claim must not re-enable another" test proves it.
  • Silent refresh (Rewards.tsx) reconciles server truth without flipping into the loading/error state, and swallows a failed background refetch so a valid page never blanks — and loadRewards(silent) never rejects, so a refresh failure can't be mis-surfaced as a claim error.
  • Idempotency — the credited note is gated on !alreadyClaimed, so a re-claim doesn't imply new money.
  • Server is the single source of truth for claimed/claimable; local state holds only the transient credited note + per-card error.
  • Coverage is excellent — claim visibility, claim→refresh→credited, idempotent re-claim, in-flight disable, per-achievement concurrency, claimed pill, backend-error surfacing, plus API-layer claimReward/normalizeClaimResult and the silent-refresh failure path.

Nicely done. A few minor / non-blocking items:

1. formatUsd ignores the app's selected language (uses browser/OS locale) — minor

RewardsCommunityTab.tsx:29

function formatUsd(value: number): string {
  return new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD' }).format(Math.max(0, value));
}

The comment says "locale-aware USD so the money glyph matches the surrounding translated sentence," but undefined resolves to the runtime locale (browser/OS), not the app's i18n language. A user who set the app to fr on an en-US machine gets $1.25 next to French text; a de-DE machine renders 1,25 $. This also diverges from formatNumber/formatTokens, which deliberately hardcode 'en-US', and makes the '$1.25 credited…' test assertion depend on Node's default locale (fine on CI today, but implicitly coupled). Suggest either pin 'en-US' for consistency, or thread the active i18n locale in — but don't leave it as ambient undefined.

2. "Claim {tokens} tokens" when rewardTokens is null — question

RewardsCommunityTab.tsx (claim button) uses formatTokens(role.rewardTokens ?? 0), so a claimable reward with rewardTokens === null renders Claim 0 tokens. Since the claim result carries both tokens and amountUsd (and the credited note shows USD), a credit-only claimable reward seems possible. Can a claimable achievement have rewardTokens: null? If so, the 0 tokens label is misleading — consider a USD/generic fallback label (e.g. reuse claimCredited's amount or a plain "Claim reward"). If claimable always implies rewardTokens > 0, ignore this.

3. Button advertises tokens, success note shows USD — question

The button says "Claim {tokens} tokens" but the credited confirmation is {amountUsd} credited to your balance. Presumably intended (tokens = headline, USD = promo credit), just confirming the two surfaces are meant to speak different units.

Verdict

The originally blocking i18n issue is fixed and the new claiming feature is solid, correct, and thoroughly tested. Approving — the three items above are minor/optional and safe to address in a follow-up or a quick touch-up.

(As before: I couldn't run the i18n scripts live — no node_modules in this worktree — so the parity/english-check results are from byte-comparison + reading the checker logic. Worth a final pnpm i18n:check && pnpm i18n:english:check + pnpm test locally to confirm green.)

@graycyrus graycyrus merged commit 4e75a63 into tinyhumansai:main Jul 3, 2026
21 of 27 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug feature Net-new user-facing capability or product behavior.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants